<?php
/**
 * バインダークラス
 */
require_once dirname(__FILE__) . '/storage.php';
class Binder extends object
{
	protected $_pages;
	protected $_label;
	protected $_storage;
	protected $_update;
	protected $_pageCount = FALSE;
	/**
	 * コンストラクタ
	 * @param unknown $label
	 * @param Storage $storage
	 */
	public function __construct($label, Storage $storage=NULL, $update=FALSE)
	{
		$this->_pages = array();
		$this->_label = $label;
		$this->_storage = $storage;
		$this->_update = $update;
		/*** 更新ありの場合、更新ロック */
		if ($update === TRUE) {
			/*** 更新ロック不要判定 */
			if ($storage->needLock($label)) {
				/*** 更新ロック中なら、更新無しで読み込み中止 */
				if ($storage->isLock()) {
					$this->_update = FALSE;
					return;
				}
				$storage->lock();
			}
		}
		if ($this->_storage != NULL) {
			$pages = $storage->fetch($label);
			if ($pages !== FALSE) {
				$this->_pages = $pages;
			}
		}
	}

	/**
	 * 更新モードを変更する
	 * @param $updateFlg
	 * @return boolean
	 */
	public function changeUpdateFlg($updateFlg=TRUE)
	{
		/*** 同一モードへの変更であれば、そのままTRUE */
		if ($this->_update === $updateFlg) {
			return TRUE;
		}
		/***
		 * 更新モードへの変更であれば、ロック状態をチェックする
		 * 参照モードへの変更であれば、更新フラグを設定するだけ
		 */
		if ($updateFlg == TRUE) {
			/*** ロック不要のストレージであれば更新フラグを設定するだけ */
			if (!$this->_storage->needLock($this->_label)) {
				$this->_update = TRUE;
				return TRUE;
			}
			/***
			 * ロック済みであれば更新できないのでFALSEを返す
			 * 未ロックであれば、ロックファイルを設定しTRUEを返す
			 */
			if ($this->_storage->isLock()) {
				return FALSE;
			} else {
				/*** ロックファイルの設置に失敗した場合、参照モードのままFALSEリターン */
				if (!$this->_storage->lock()) {
					return FALSE;
				}
				$this->_update = TRUE;
				return TRUE;
			}
		} else {
			$this->_update = FALSE;
			return TRUE;
		}
		return FALSE;
	}

	/**
	 * デストラクタ
	 * ストレージが設定されている場合はそこに保管する
	 * @return boolean
	 */
	public function __destruct()
	{
		if ($this->_storage != NULL) {
			if ($this->_update === TRUE) {
				$this->_storage->store($this->_label, $this->_pages);
				/*** 更新ロック中ならロックを解除する */
				if ($this->_storage->isLock()) {
					$ret = $this->_storage->unLock();
					/*** 更新ロック解除に失敗した場合、アラートメールを管理者に発送する */
					if ($ret === FALSE) {
						$this->_sendMailUnlockAlert();
					}
				}
			}
		}
	}

	/**
	 * 更新ロック解除失敗アラートメール
	 * @return mixed
	 */
	protected function _sendMailUnlockAlert()
	{
		$from = 'jsc@minder.minibird.jp';
		$subject = 'アンロックの解除に失敗しました。';
		$text = $this->_label;
		$ret = Mail_send_function('hariushi@gmail.com', $subject, $text, $from);
		return $ret;
	}

	/**
	 *
	 * @param unknown $pageNum
	 * @return Ambigous <multitype:, object, unknown>
	 */
	public function page($pageNum)
	{
		/*** 連想配列から添え字配列に変換する */
		$ary = array_values($this->_pages);
		if (isset($ary[$pageNum])) {
			return $ary[$pageNum];
		}
		return FALSE;
	}

	/**
	 * ページ数を返す
	 * @return number
	 */
	public function get_pagecount()
	{
		/*** ページ数を計算していない場合は計算する */
		if ($this->_pageCount === FALSE) {
			$this->_pageCount = count($this->_pages);
		}
		return $this->_pageCount;
	}

	/**
	 * ページ配列を返す
	 * @return Ambigous <multitype:, object, unknown>
	 */
	public function get_pages()
	{
		return $this->_pages;
	}

	/**
	 * ページを綴じる
	 * @param object $page
	 * @return boolean
	 */
	public function bind(object $page)
	{
		$this->_pages[$page->get_uid()] = $page;
		return TRUE;
	}

	/**
	 * 渡されたバインダーの中身との論理積でバインダーを作る
	 * @param Binder $binder
	 * @return Binder
	 */
	public function crossover(Binder $binder)
	{
		$result = new Binder('');
		$crossoveredPage = array();
		$pages = $binder->get_pages();
		/*** ページ捜査 */
		foreach ($pages as $page) {
			/*** 同じページならバインダーに綴じる */
			if (array_key_exists($page->Uid, $this->_pages) === TRUE) {
				$result->bind($page);
			}
		}
		return $result;
	}

	/**
	 * 渡されたバインダーの中身と会わせたバインダーを作成する
	 * 重複するページは片方を破棄する
	 * @param Binder $binder
	 * @return Binder
	 */
	public function marge(Binder $binder)
	{
		$result = new Binder('');
		/***自分のページを全て綴じる */
		foreach ($this->_pages as $page) {
			$result->bind($page);
		}
		$pages = $binder->get_pages();
		/*** 渡されたページを全て綴じる */
		foreach ($pages as $page) {
			/*** 重複するページは破棄する */
			if (key_exists($page->Uid, $this->_pages) === FALSE) {
				$result->bind($page);
			}
		}
		return $result;
	}

	/**
	 * 全てのページ（オブジェクト）のプロパティについて値を評価し
	 * 条件に一致するページでバインダーを作成する。
	 * $methodパラメータはobjectクラスに定義されているSELECT::*を指定する
	 * @see object
	 * @param unknown $property
	 * @param unknown $value
	 * @param unknown $method
	 * @return Binder
	 */
	public function select($property,$value,$method)
	{
		$result = new Binder('');
		foreach ($this->_pages as $page) {
			if ($page->isYou($property,$value,$method) === TRUE) {
				$result->bind($page);
			}
		}
		return $result;
	}

	/**
	 * プロパティがバリューに一致するページでバインダーを作成する
	 * @param unknown $property
	 * @param unknown $value
	 * @return Binder
	 */
	public function selectAgree($property,$value)
	{
		return $this->select($property, $value, object::SELECT_AGREE);
	}

	/**
	 * プロパティがバリューに一致しないページでバインダーを作成する
	 * @param unknown $property
	 * @param unknown $value
	 * @return Binder
	 */
	public function selectDisagree($property,$value)
	{
		return $this->select($property, $value, object::SELECT_DISAGREE);
	}

	/**
	 * プロパティがバリューより大きいページでバインダーを作成する
	 * @param unknown $property
	 * @param unknown $value
	 * @return Binder
	 */
	public function selectLarger($property,$value)
	{
		return $this->select($property, $value, object::SELECT_LARGER);
	}

	/**
	 * プロパティがバリューより小さいページでバインダーを作成する
	 * @param unknown $property
	 * @param unknown $value
	 * @return Binder
	 */
	public function selectSmaller($property,$value)
	{
		return $this->select($property, $value, object::SELECT_SMALLER);
	}

	/**
	 * プロパティがバリューに部分一致するページでバインダーを作成する
	 * @param unknown $property
	 * @param unknown $value
	 * @return Binder
	 */
	public function selectLike($property,$value)
	{
		return $this->select($property, $value, object::SELECT_LIKE);
	}


	/**
	 * 全てのページ（オブジェクト）のプロパティについて値を評価し
	 * 条件に一致するページを返す。
	 * $methodパラメータはobjectクラスに定義されているSELECT::*を指定する。
	 * @see object
	 * 条件に一致するページが複数ある場合は最初にヒットしたページを返す。
	 * @param unknown $property
	 * @param unknown $value
	 * @param unknown $method
	 * @return Ambigous <multitype:, object, unknown>|boolean
	 */
	public function selectPage($property,$value,$method)
	{
		foreach ($this->_pages as $page) {
			if ($page->isYou($property,$value,$method) === TRUE) {
				return $page;
			}
		}
		return FALSE;
	}

	/**
	 * プロパティがバリューに一致するページを返す
	 * @param unknown $property
	 * @param unknown $value
	 * @return Ambigous <Ambigous, boolean, multitype:, object, unknown>
	 */
	public function selectAgreePage($property,$value)
	{
		return $this->selectPage($property, $value, object::SELECT_AGREE);
	}

}

/**
 * 単体テストコードブロック
 */
if (class_exists('UnitTestCase')) {
	require_once dirname(__FILE__) . '/diskunit.php';
	require_once dirname(__FILE__) . '/../Mcrypt.php';

	class Person extends object
	{
		public $Name;
		public $Age;
	}

	class BinderTest extends UnitTestCase {
		/**
		 * ストレージなしのバインダーテスト
		 */
		public function test1000() {
			$binder = new Binder ( '会員');
			$Ckun = new Person ();
			$Ckun->Name = 'はは';
			$Ckun->Age = 18;
			$binder->bind( $Ckun );
			$name = $binder->page(0)->Name;
			$this->assertEqual($name, 'はは');
		}
		/**
		 * ストレージ保存テスト
		 */
		public function test1100(){
			$storage = new Storage_Diskunit  ( 'バインダーテスト', dirname ( __FILE__ ) . '/' );
			$binder = new Binder ( '会員', $storage );
			$Akun = new Person ();
			$Akun->Name = 'はは';
			$Akun->Age = 40;
			$binder->bind( $Akun );
			$Bkun = new Person ();
			$Bkun->Name = 'ちち';
			$Bkun->Age = 45;
			$binder->bind( $Bkun );
			$Ckun = new Person ();
			$Ckun->Name = 'となりのはは';
			$Ckun->Age = 45;
			$binder->bind( $Ckun );
		}

		/**
		 * ストレージ読み込みテスト
		 */
		public function test1200()
		{
			$storage = new Storage_Diskunit  ( 'バインダーテスト', dirname ( __FILE__ ) . '/' );
			$binder = new Binder ( '会員', $storage );
			$pageCount  = $binder->Pagecount;
			$i = 0;
			while ($i < $pageCount) {
				echo $binder->page($i)->Name .' : '. $binder->page($i)->Age;
				$i ++;
			}
		}

		/**
		 * 検索テスト
		 */
		public function test1300()
		{
			$storage = new Storage_Diskunit  ( 'バインダーテスト', dirname ( __FILE__ ) . '/' );
			$binder = new Binder ( '会員', $storage );
			$nameSelectedBinder = $binder->selectAgree('Name', 'はは');
			$this->assertEqual($nameSelectedBinder->page(0)->Name, 'はは');
			$selectAge45 = $binder->selectAgree('Age', 45);
			$this->assertEqual($selectAge45->Pagecount, 2);
			$meargeBinder = $nameSelectedBinder->marge($selectAge45);
			$this->assertEqual($meargeBinder->Pagecount,3);
			$crossoverBinder = $selectAge45->crossover($nameSelectedBinder);
			$likeHahaBinder = $binder->selectLike('Name', 'はは');
			$this->assertEqual($likeHahaBinder->Pagecount, 2);
			$crossoverBinder2 = $likeHahaBinder->crossover($selectAge45);
			$this->assertEqual($crossoverBinder2->Pagecount, 1);
		}
	}
}